package gui.inventory;

import gui.common.*;
import gui.item.*;
import gui.product.*;

import java.util.*;

import core.Container;
import core.Product;
import core.RootStorageUnit;
import core.StorageUnit;
import core.ProductGroup;
import core.ContainerFacade;
import core.Item;
import core.ItemFacade;
import core.Notification;
import core.ProductFacade;
import core.exception.ModelException;
import core.exception.NotFoundException;

/**
 * Controller class for inventory view.
 */

public class InventoryController extends Controller implements IInventoryController, Observer
{
	private static ContainerFacade _cFacade = ContainerFacade.instance();
	private static ItemFacade _iFacade = ItemFacade.instance();
	private static ProductFacade _pFacade = ProductFacade.instance();
	
	private Map<Container, ProductContainerData> containerToDataMap;	
	private Map<Product, ProductData> productToDataMap;
	private Map<Item, ItemData> itemToDataMap;
	
	/**
	 * Constructor.
	 *  
	 * @param view Reference to the inventory view
	 */
	public InventoryController(IInventoryView view) 
	{
		super(view);
		_cFacade.addObserver(this);
		_iFacade.addObserver(this);
		_pFacade.addObserver(this);
		
		containerToDataMap = new HashMap<Container, ProductContainerData>();
		productToDataMap = new HashMap<Product, ProductData>();
		itemToDataMap = new HashMap<Item, ItemData>();

		buildContainerMap();
		
		construct();
	}

	/**
	 * Returns a reference to the view for this controller.
	 */
	@Override
	protected IInventoryView getView() {
		return (IInventoryView)super.getView();
	}

	/**
	 * Loads data into the controller's view.
	 * 
	 *  {@pre None}
	 *  
	 *  {@post The controller has loaded data into its view}
	 */
	@Override
	protected void loadValues() {
		// save selections
		ProductContainerData selectedContainer = getView().getSelectedProductContainer();
		ProductData selectedProduct = getView().getSelectedProduct();
		ItemData selectedItem = getView().getSelectedItem();
		
		// load values
		Container c = selectedContainer == null? null : selectedContainer.getContainer();
		Product p = selectedProduct == null? null : selectedProduct.getProduct();
		
		updateContextPanel(c);
		displayProductsInContainer(c);
		displayItemsForProductAndContainer(p,c);
		
		// restore selections
		getView().selectItem(selectedItem);
		getView().selectProduct(selectedProduct);
		getView().selectProductContainer(selectedContainer);
	}
	
	/**
	 * Update the context panel for the current container
	 * @param container
	 */
	private void updateContextPanel(Container container) {
		//set the context view panels
		if (container == null){
			getView().setContextGroup("");
			getView().setContextSupply("");
			getView().setContextUnit("");
		}
		else if (container instanceof RootStorageUnit) {
			getView().setContextUnit("ALL");
			getView().setContextGroup("");
			getView().setContextSupply("");
		}
		else if (container instanceof StorageUnit) {
			getView().setContextUnit(container.getName());
			getView().setContextGroup("");
			getView().setContextSupply("");
		}
		else {
			//product group
			ProductGroup pg = (ProductGroup)container;
			StorageUnit su = pg.getStorageUnit();
			getView().setContextUnit(su.getName());
			getView().setContextGroup(pg.getName());
			if (pg.getThreeMonthSupply().getQuantity() == 0)
				getView().setContextSupply("");
			else
				getView().setContextSupply(pg.getThreeMonthSupply().toString());
		}
	}	
	
	/**
	 * Display the products in the container
	 * @param c the container
	 */
	private void displayProductsInContainer(Container c) {
		Iterator<Product> iter = null;
		
		if (c == null)
			iter = null;
		else if (c instanceof RootStorageUnit)
			iter = ProductFacade.instance().getAllProducts();
		else{
			try {
				iter = ProductFacade.instance().getProductsForContainer(c);
			} catch (NotFoundException e) {
				iter = null; // no products
			}
		}
		
		ArrayList<ProductData> data = new ArrayList<ProductData>();
		if (iter != null){
			while (iter.hasNext()){
				ProductData toAdd = getProductData(iter.next());
				setCount(toAdd,c);
				data.add(toAdd);
			}
		}
		
		Collections.sort(data);
		getView().setProducts(data.toArray(new ProductData[0]));
	}
	
	/**
	 * Set the count for the product data and container.
	 * @param data
	 * @param container
	 */
	private void setCount(ProductData data, Container container){
		assert (container != null);
		if (container instanceof RootStorageUnit)
			data.setCount(ItemFacade.instance().getNumberOfItemsInProduct(data.getBarcode()));
		else
			data.setCount(ItemFacade.instance().
					getNumItemsInProductAndContainer(data.getProduct(), container));
	}	
	
	/**
	 * Load information for the itemData table
	 */
	private void displayItemsForProductAndContainer(Product product, Container container) {
		Iterator<Item> iter;
		
		if (product == null || container == null)
			iter = null;
		else if (container instanceof RootStorageUnit){
			try {
				iter = ItemFacade.instance().getItemsInProduct(product.getBarcode().toString());
			} catch (NotFoundException e) {
				iter = null; // no items
			}
		}
		else
			iter = ItemFacade.instance().getItemsInProductAndContainer(product, container);
		
		List<ItemData> itemDataList = new ArrayList<ItemData>();	
		if (iter != null){
			while (iter.hasNext()){
				itemDataList.add(getItemData(iter.next()));
			}
		}
		
		Collections.sort(itemDataList);
		getView().setItems(itemDataList.toArray(new ItemData[0]));
	}
	
	
	/**
	 * rebuilds the container map and updates the view of any added/changed containers
	 */
	private void buildContainerMap() {
		ProductContainerData root = new ProductContainerData(_cFacade.get_rootUnit());		
		getView().setProductContainers(root);
		containerToDataMap.clear();
		recursiveBuildMap(root);
	}
	
	/**
	 * Don't call this method directly.
	 */
	private void recursiveBuildMap(ProductContainerData pcd) {
		containerToDataMap.put((Container)pcd.getTag(), pcd);
		for (int i = 0; i < pcd.getChildCount(); i++) {
			recursiveBuildMap(pcd.getChild(i));
		}
	}
	
	private ProductContainerData getContainerData(Container c){
		if(!containerToDataMap.containsKey(c)){
			ProductContainerData data = new ProductContainerData(c);
			containerToDataMap.put(c, data);
		}
		return containerToDataMap.get(c);		
	}
	
	/**
	 * Return an itemData for an item
	 */
	private ItemData getItemData(Item i){
		if(!itemToDataMap.containsKey(i)){
			ItemData data = new ItemData(i);
			itemToDataMap.put(i, data);
		}
		itemToDataMap.get(i).reloadItemInfo();
		return itemToDataMap.get(i);		
	}
	
	/**
	 * Return a productData for a product
	 * @param p the product
	 * @return
	 */
	private ProductData getProductData(Product p){
		if(!productToDataMap.containsKey(p)){
			ProductData data = new ProductData(p);
			productToDataMap.put(p, data);
		}
		productToDataMap.get(p).reloadProductData();
		return productToDataMap.get(p);
	}
	

	/**
	 * Sets the enable/disable state of all components in the controller's view.
	 * A component should be enabled only if the user is currently
	 * allowed to interact with that component.
	 * 
	 * {@pre None}
	 * 
	 * {@post The enable/disable state of all components in the controller's view
	 * have been set appropriately.}
	 */
	@Override
	protected void enableComponents() 
	{
		return;
	}
	
	/**
	 * Returns true if and only if the "Add Storage Unit" menu item should be enabled.
	 */
	@Override
	public boolean canAddStorageUnit() 
	{
		return true;
	}
	
	/**
	 * Returns true if and only if the "Add Items" menu item should be enabled.
	 */
	@Override
	public boolean canAddItems() 
	{
		return true;
	}
	
	/**
	 * Returns true if and only if the "Transfer Items" menu item should be enabled.
	 */
	@Override
	public boolean canTransferItems() {
		return true;
	}
	
	/**
	 * Returns true if and only if the "Remove Items" menu item should be enabled.
	 */
	@Override
	public boolean canRemoveItems() {
		return true;
	}

	/**
	 * Returns true if and only if the "Delete Storage Unit" menu item should be enabled.
	 */
	@Override
	public boolean canDeleteStorageUnit() {
		ProductContainerData data = getView().getSelectedProductContainer();
		return ContainerFacade.instance().canRemoveContainer(data.getContainer());
		
	}
	
	/**
	 * This method is called when the user selects the "Delete Storage Unit" menu item.
	 */
	@Override
	public void deleteStorageUnit() {
		ProductContainerData data = getView().getSelectedProductContainer();
		try {
			ContainerFacade.instance().removeContainer(data.getContainer());
		}
		catch (ModelException e) {
			_view.displayErrorMessage("Unable to delete storage unit " + data.getName());
		}
		
	}

	/**
	 * Returns true if and only if the "Edit Storage Unit" menu item should be enabled.
	 */
	@Override
	public boolean canEditStorageUnit() {
		return true;
	}

	/**
	 * Returns true if and only if the "Add Product Group" menu item should be enabled.
	 */
	@Override
	public boolean canAddProductGroup() {
		return true;
	}

	/**
	 * Returns true if and only if the "Delete Product Group" menu item should be enabled.
	 */
	@Override
	public boolean canDeleteProductGroup() {
		ProductContainerData data = getView().getSelectedProductContainer();
		return ContainerFacade.instance().canRemoveContainer(data.getContainer());
	}

	/**
	 * Returns true if and only if the "Edit Product Group" menu item should be enabled.
	 */
	@Override
	public boolean canEditProductGroup() {
		return true;
	}
	
	/**
	 * This method is called when the user selects the "Delete Product Group" menu item.
	 */
	@Override
	public void deleteProductGroup() {
		ProductContainerData data = getView().getSelectedProductContainer();
		try {
			ContainerFacade.instance().removeContainer(data.getContainer());
		}
		catch (ModelException e) {
			_view.displayErrorMessage("Unable to delete product group " + data.getName());
		}
	}

	/**
	 * This method is called when the selected item container changes.
	 */
	@Override
	public void productContainerSelectionChanged() {
		getView().setProducts(new ProductData[0]);
		getView().setItems(new ItemData[0]);
		loadValues();
	}
	


	/**
	 * This method is called when the selected item changes.
	 */
	@Override
	public void productSelectionChanged() 
	{
		loadValues();
	}


	/**
	 * This method is called when the selected item changes.
	 */
	@Override
	public void itemSelectionChanged() 
	{
		return;
	}

	/**
	 * Returns true if and only if the "Delete Product" menu item should be enabled.
	 */
	@Override
	public boolean canDeleteProduct() {
		ProductData productData = getView().getSelectedProduct();
		ProductContainerData containerData = getView().getSelectedProductContainer();
		if (productData == null || containerData == null) return false;
		return ProductFacade.instance().canRemoveProduct(productData.getProduct(),
														 containerData.getContainer());
	}

	/**
	 * This method is called when the user selects the "Delete Product" menu item.
	 */
	@Override
	public void deleteProduct() {
		ProductData productData = getView().getSelectedProduct();
		ProductContainerData containerData = getView().getSelectedProductContainer();
		try {
			ProductFacade.instance().removeProduct(productData.getProduct(), 
												   containerData.getContainer());
		}
		catch (ModelException e) 
		{
			_view.displayErrorMessage("Unable to delete product: " + productData.getDescription());
		}
	}

	/**
	 * Returns true if and only if the "Edit Item" menu item should be enabled.
	 */
	@Override
	public boolean canEditItem() {
		ItemData iData = getView().getSelectedItem();
		return iData != null;
	}

	/**
	 * This method is called when the user selects the "Edit Item" menu item.
	 */
	@Override
	public void editItem() 
	{
		getView().displayEditItemView();
	}

	/**
	 * Returns true if and only if the "Remove Item" menu item should be enabled.
	 */
	@Override
	public boolean canRemoveItem() {
		ItemData data = getView().getSelectedItem();		
		if (data == null || data.getItem() == null)
			return false;
		return ItemFacade.instance().canRemoveItem(data.getItem());
	}

	/**
	 * This method is called when the user selects the "Remove Item" menu item.
	 */
	@Override
	public void removeItem() {
		ItemData data = getView().getSelectedItem();
		try {
			ItemFacade.instance().removeItem(data.getItem());
		}
		catch (ModelException e) {
			_view.displayErrorMessage("Unable to remove item");
		}
	}

	/**
	 * Returns true if and only if the "Edit Product" menu item should be enabled.
	 */
	@Override
	public boolean canEditProduct() {
		ProductData pData = getView().getSelectedProduct();
		return pData != null;
	}

	/**
	 * This method is called when the user selects the "Add Product Group" menu item.
	 */
	@Override
	public void addProductGroup() {
		getView().displayAddProductGroupView();
	}
	
	/**
	 * This method is called when the user selects the "Add Items" menu item.
	 */
	@Override
	public void addItems() {
		getView().displayAddItemBatchView();
	}
	
	/**
	 * This method is called when the user selects the "Transfer Items" menu item.
	 */
	@Override
	public void transferItems() {
		getView().displayTransferItemBatchView();
	}
	
	/**
	 * This method is called when the user selects the "Remove Items" menu item.
	 */
	@Override
	public void removeItems() {
		getView().displayRemoveItemBatchView();
	}

	/**
	 * This method is called when the user selects the "Add Storage Unit" menu item.
	 */
	@Override
	public void addStorageUnit() {
		getView().displayAddStorageUnitView();
	}

	/**
	 * This method is called when the user selects the "Edit Product Group" menu item.
	 */
	@Override
	public void editProductGroup() {
		getView().displayEditProductGroupView();
	}

	/**
	 * This method is called when the user selects the "Edit Storage Unit" menu item.
	 */
	@Override
	public void editStorageUnit() {
		getView().displayEditStorageUnitView();
	}

	/**
	 * This method is called when the user selects the "Edit Product" menu item.
	 */
	@Override
	public void editProduct() {
		getView().displayEditProductView();
	}
	
	/**
	 * This method is called when the user drags a product into a
	 * product container.
	 * 
	 * @param productData Product dragged into the target product container
	 * @param containerData Target product container
	 */
	@Override
	public void addProductToContainer(ProductData productData, 
										ProductContainerData containerData) {
		Container c = (Container)containerData.getTag();
		Product p = (Product)productData.getTag();
		
		if (!ProductFacade.instance().canAddProductToContainer(p, c))
			return;
		
		try {
			ProductFacade.instance().addProductToContainer(p, c);
		}
		catch (ModelException e) {
			_view.displayErrorMessage("Unable to add product " + productData.getDescription() +
					" to container " + containerData.getName());
		}
	}

	/**
	 * This method is called when the user drags an item into
	 * a product container.
	 * 
	 * @param itemData Item dragged into the target product container
	 * @param containerData Target product container
	 */
	@Override
	public void moveItemToContainer(ItemData itemData,
									ProductContainerData containerData) {
		try {
			ItemFacade.instance().moveItem(itemData.getItem(), containerData.getContainer());
		}
		catch (ModelException e) {
			_view.displayErrorMessage("Unable to move the item to container " + 
					containerData.getName());
		}
	}

	@Override
	public void update(Observable o, Object arg) 
	{
		update((Notification)arg);		
		loadValues();

	}

	/**
	 * Internal update method that responds to notifications
	 * @param n
	 */
	private void update(Notification n) {
		Container container;
		switch(n.getType()){
		case ItemRemoved: 
			getView().selectItem(null);
			break;
		case ProductRemoved:
			getView().selectProduct(null);
			getView().selectItem(null);
			break;
		case ContainerRemoved:
			container = (Container)n.getTag();
			getView().deleteProductContainer(getContainerData(container));
			containerToDataMap.remove(container);
			getView().selectProductContainer(null);	
			break;
		case ContainerAdded:
			container = (Container)n.getTag();
			getView().insertProductContainer(
					getContainerData(container.getParent()), getContainerData(container), 
					getIndexOfChild(container));
			
			getView().selectProductContainer(getContainerData(container));		
			break;
		case ContainerChanged:
			container = (Container)n.getTag();
			getView().renameProductContainer(getContainerData(container), 
					container.getName(), 
					getIndexOfChild(container));	
			getView().selectProductContainer(getContainerData(container));
			break;
		default:
			break;		
		}
	}

	/**
	 * Get the index of the child container in it's parent
	 * @param child
	 * @return
	 */
	private int getIndexOfChild(Container child) {
		Container parent = child.getParent();
		Iterator<Container> children = parent.getChildContainers();
		int index = 0;
		while(children.hasNext()){
			if(children.next().equals(child))
				break;
			index ++;
		}
		return index;
	}

}

